#####################################################################
# File:         UserAccessManager.py
# Product:      KnowledgeEngine
#
# Copyright:    Copyright (c) 2002, Faulkner Ltd.
# Author:       Tom Cameron
#
# Description:  This file defines the UserAccessManager class, a simple
#               container defining all User-Model-Assessment relationships
#               (UserAccessRules) for an Environment
#
# See Also:     ElementContainer, Element
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.KnowledgeEngineCore import *
from Products.KnowledgeEngine.ElementContainer import ElementContainer, ElementContainerInfo
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from ImageFile import ImageFile

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## UserAccessManagerInfo Class Definition
class UserAccessManagerInfo(ElementContainerInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return "UserAccessManager"

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """

        return "User Access Manager"

    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.
        """

        return "uam"

    #---------------------------------------------------------------#

    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """

        return ImageFile('www/icon.gif', globals())

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a
            child of the class' instances.

            NOTE: This is from this class' instance perspective.

            For elements containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """

        #UserAccessManager may only contain UserAccessRules (see below)
        return childClassInfo.isInstanceOf('UserAccessRules')

    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in
            the specified container (containerInstance)

            NOTE: This is from this class' instance perspective.
        """

        #UserAccessManager are only permitted in Environments (but you're never allowed to add them yourself)
        return containerClassInfo.isInstanceOf('Environment') and (containerInstance == None or (containerInstance != None and containerInstance.isInstanceOf('Environment') and containerInstance.getUserAccessManager() == None))


    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific properties using the PropertyReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class properties for reflection
        ElementContainerInfo.onConfigureProperties(self)

        #set that the default 'id' for the user access manager is it's identity type (uam)
        self.getPropertyDefinition('id').setDefaultValue(self.getIdentityType())

        #add the 'User Properties' property
        self.addPropertyDefinition(PropertyDefinition(identity="userProperties", displayName="User Properties", description="Place each property on a new line", defaultValue="Group\nHome Phone\nWork Phone", propertyType=pt_STRING, propertyFormat=pf_TEXT_MULTILINE, propertyStructure=ps_ATOMIC))

        #set all of the properties to be not usable for constructors!
        #this will ensure that a constructor will not be built for this
        #type of component
        for propertyDefinition in self.getPropertyDefinitions():
            propertyDefinition.setIsForConstructor(false)

    #---------------------------------------------------------------#

#####################################################################
## UserAccessManager Class Definition
class UserAccessManager(ElementContainer):
    """
        A class to represent the UserAccessManager for an Environment
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = UserAccessManagerInfo()

    #---------------------------------------------------------------#

    # Custom zope manage_main for containers (to display contents by Order attribute)
    manage_main = DTMLFile('dtml/manage_users', globals())
    manage_help = DTMLFile('dtml/help', globals())
    edit_user_details = DTMLFile('dtml/edit_user_details', globals())
    model_access_rules = DTMLFile('dtml/model_access_rules', globals())

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the UserAccessManager"

        ElementContainer.__init__(self, id, identity)

        #Always add UserAccessRules for the Anonymous User
        REQUEST = {}
        REQUEST['username']='Anonymous User'
        self.manage_ObjectConstruction(REQUEST, "CREATE_INSTANCE", "UserAccessRules")

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object
            has been loaded into memory.

            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation processing
        ElementContainer.onAfterInstanciation(self)

        #add/update the Contents tab to the front of the tab order for managing the container
        self.addManagementTab('Contents', 'manage_main', None, true)

        #add the Help tab to the end of the tab order for managing the container
        self.addManagementTab('Help', 'manage_help', None, false)

    #---------------------------------------------------------------#

    def onAfterAdd(self):
        """
            Handle Knowledge Engine specific actions after this object has been added to a container
        """

        ElementContainer.onAfterAdd(self)
        self.getEnvironment().registerUserAccessManager(self)

    #---------------------------------------------------------------#

    def onBeforeDelete(self):
        """
            Handle Knowledge Engine specific actions before this object is deleted
        """

        ElementContainer.onBeforeDelete(self)
        self.getEnvironment().deregisterUserAccessManager(self)

    #---------------------------------------------------------------#

    def getUserAccessRules(self, username):
        """
            Returns the UserAccessRules object for the specified user
            (if one exists), otherwise returns None.
        """

        result = None
        for child in self.getChildren():
            if child.getUserName() == username:
                result = child

        return result

    #---------------------------------------------------------------#

    def getUsersByModel(self, modelidentity):
        """
            Returns a list of the users who have access (are linked)
            to a given Model.
        """

        allUsers = self.getEnvironment().getAllUsers()
        ret = []
        for u in allUsers:
            if self.getUserAccessRules(u.getUserName()) != None:
                if self.getUserAccessRules(u.getUserName()).hasAccess(modelidentity):
                    ret = ret + [u]
        return ret

    #---------------------------------------------------------------#

    def getUsersSortedByLastname(self):
        """
            Returns a list of the users sorted by sorted by lastname
        """

        uf = self.getUserFolders()
        
        for UserFolder in uf:
            u = UserFolder.getUsers()
        
        key = 'lastname'
        u.sort(lambda x, y: cmp(x.getProperty("lastname"), y.getProperty("lastname")))

        return u

    #---------------------------------------------------------------#

    def getUserProperties(self):
        """
            Returns the userProperties as a proper list
        """

        return self.userProperties.splitlines(false) #false = drop eond of lines

    #---------------------------------------------------------------#

    def toExUserFolderProperty(self, id):
        """Converts a property name like - First Name
           to an exUserFolder property like - user_First_Name
        """

        id=string.strip(id)
        id=string.replace(id,"\t","")
        id=string.replace(id,"\n","")
        id=string.replace(id," ","_")
        id=string.replace(id,"'","")
        id=string.replace(id,'"','')
        id=string.replace(id,"!","")
        id=string.replace(id,"@","")
        id=string.replace(id,"#","")
        id=string.replace(id,"$","")
        id=string.replace(id,"%","")
        id=string.replace(id,"^","")
        id=string.replace(id,"&","")
        id=string.replace(id,"*","")
        id=string.replace(id,"(","")
        id=string.replace(id,")","")
        id=string.replace(id,"-","")
        id=string.replace(id,"+","")
        id=string.replace(id,"=","")
        id=string.replace(id,"[","")
        id=string.replace(id,"]","")
        id=string.replace(id,"{","")
        id=string.replace(id,"}","")
        id=string.replace(id,"\\","")
        id=string.replace(id,"/","")
        id=string.replace(id,"|","")
        id=string.replace(id,"'","")
        id=string.replace(id,":","")
        id=string.replace(id,"?","")
        id=string.replace(id,";","")
        id=string.replace(id,"<","")
        id=string.replace(id,">","")
        id=string.replace(id,".","_")
        id=string.replace(id,",","")
        id=string.replace(id,"`","")

        return id

    #---------------------------------------------------------------#

    def getUserByAssessmentId(self, assessmentidentity):
        """
            Returns the user who has access to the
            assessment.

            This is one ugly task, you have to test
            each user one-by-one and then check their models. and then
            check their assessments until you get a match.

            NOTE: if we ever decide to allow multiple users access
            the same assessment then this will be broken and we will
            need to store a dictionary of user/assessments or something

            Returns the user object
        """

        allUsers = self.getEnvironment().getAllUsers()
        ret = []
        for u in allUsers:
            if self.getUserAccessRules(u.getUserName()) != None:
                aids = []
                assessmentslist = self.getUserAccessRules(u.getUserName()).getAssessments()
                for a in assessmentslist:
                    aids = aids + [a[0]]
                if assessmentidentity in aids:
                    return u
        return "unknown"


    #---------------------------------------------------------------#

    def searchExUserFolder(self, REQUEST=None):
        """
            Searches through exUserFolder user attributes to return a list
            of specific users by id that match search criteria
        """

        #### build an index (dictionary) of search indices (dictionaries) for each user attribute
        
        # the "master" search index
        search_index = {}

        # the indices corresponding to each predefined exUserFolder user attribute
        username_index = {}
        firstname_index = {}
        lastname_index = {}
        email_index = {}
        roles_index = {}

        allUsers = self.getUsersSortedByLastname()        allUsernames = [user.getUserName() for user in allUsers]

        for user in allUsers:
            username_index[user.getUserName()] = user.getUserName()
            firstname_index[user.getUserName()] = user.getProperty("firstname")
            lastname_index[user.getUserName()] = user.getProperty("lastname")
            email_index[user.getUserName()] = user.getProperty("email")
            roles_index[user.getUserName()] = user.roles # returns a list!
        search_index['username'] = username_index
        search_index['firstname'] = firstname_index
        search_index['lastname'] = lastname_index
        search_index['email'] = email_index
        search_index['roles'] = roles_index
        
        # need to create additional indices for each user-defined property in getUserProperties
        for prop in self.getUserProperties():
            temp_index = {}
            for user in allUsers:
                value = user.getProperty(self.toExUserFolderProperty(prop))
                if value == None:
                    temp_index[user.getUserName()] = ''
                else:
                    temp_index[user.getUserName()] = value
            # and add them to the search index
            search_index[prop] = temp_index
            
        #### for each search value entered in search form, search corresponding search index 
        #### and construct a list (list) of per attribute search results (list of usernames)
        
        search_results = []
        errors = []
        for key in search_index.keys():
            temp_list = []
            if key == 'roles': #special case because roles is a list
                if REQUEST.form['roles'] != ['']:
                    for user in allUsernames:
                        for role in search_index['roles'][user]:
                            if role in REQUEST.form['roles']:
                                if user not in temp_list: temp_list.append(user)
                    if len(temp_list) == 0: temp_list=[key] # need to return something (ideally unique), so that we can differentiate between no result and an empty search criteria 
            elif key in ['username', 'firstname', 'lastname', 'email']:
                if REQUEST.form[key] != '':
                    temp_list = [item for item in allUsernames if search_index[key][item].lower().find(REQUEST.form[key].lower()) != -1 ]
                    if len(temp_list) == 0: temp_list=[key] # need to return something (ideally unique), so that we can differentiate between no result and an empty search criteria 
            else: # user-defined exUserFolder attributes are renamed, so we handle them differently
                if REQUEST.form['user_' + self.toExUserFolderProperty(key)] != '':
                    temp_list = [item for item in allUsernames if search_index[key][item].lower().find(REQUEST.form['user_' + self.toExUserFolderProperty(key)].lower()) != -1 ]
                    if len(temp_list) == 0: temp_list=[key] # need to return something (ideally unique), so that we can differentiate between a "no search results" and an "empty search criteria" result
            search_results.append(temp_list)
                
        #### need to determine which search results are meaningful (i.e. which results match *all* of the search criteria)
        
        # filter and return only the search criteria that actually returned results 
        results = [item for item in search_results if len(item) != 0]
        
        # if len(results) == 0, this means that there were no matches and that we can return None
        if len(results) == 0:
            return [] 
        # if len(results) == 1, this means that one criteria returned matches, and that we can simply return the results for that criteria
        elif len(results) == 1:
            final_results = []
            temp_list = [item for item in search_results if len(item) != 0][0]
            for user in temp_list:
                if user in allUsernames:
                    final_results.append(user)
            return final_results
        # multiple criteria returned results, so we need to determine which user/s matched *all* of the criteria
        else:
            # determine unique users by first making a list of all returned users in results
            combined_users = []
            for item in results:
                for user in item:
                    combined_users.append(user)
            # then determine unique users using dictionaries-must-have-unique-keys behaviour 
            d = {}
            for item in combined_users:
               d[item] = 1
            unique_users = d.keys()
            # now work out which of these unique users match *all* of the criteria
            len_results = len(results)
            final_results = []
            for user in unique_users:
                x = 0
                for result in results:
                    if user in result:
                        x = x + 1
                if x == len_results:
                    # finally filter and return unique_users that are actual users, i.e. don't include dummy results that signify that a search criteria was entered (in order to differentiate from empty criteria)
                    if user in allUsernames:
                        final_results.append(user)
            # unfortunately, results are no longer sorted by lastname (because we have passed the results through a dictionary)
            # but we can re-sort by doing a list comprehension of the results against the original sorted list of usernames.
            final_sorted_results = [item for item in allUsernames if item in final_results]
            return final_sorted_results   

    #---------------------------------------------------------------#

    def getUserAttributeValues(self, userattribute):
        """
            Returns a list of entered values for exUserFolder user attributes
            - used to populate dropdowns on search form
        """

        value_list = []

        allUsers = self.getEnvironment().getAllUsers()

        if userattribute in ['firstname','lastname','email']:
            for user in allUsers:
                if user.getProperty(userattribute) != '': 
                    value_list.append(user.getProperty(userattribute))
        elif userattribute == 'username':
            for user in allUsers:
                if user.getUserName() != '':
                    value_list.append(user.getUserName())
        else:
            for user in allUsers:
                value = user.getProperty(self.toExUserFolderProperty(userattribute))
                if value not in ['',None,'None'] and value not in value_list:
                    value_list.append(value)
        
        return value_list

    #---------------------------------------------------------------#

    def addAdditionalAssessments(self, REQUEST=None):
        """
            Adds additional assessments to USERACCESSRULES, given a REQUEST object containing a list of users
            and a series of (modelidentity):(number to increase maxassessments by) pairs
        """
        # for reference: the USERACCESSRULES.models dictionary has the following structure
        #
        # models = {
        #        'modelidentity1':{'maxassessments':5, 'status':'open'},
        #        'modelidentity2':{'maxassessments':5, 'status':'open'},
        #        'modelidentity3':{'maxassessments':6, 'status':'lock'},
        #        'modelidentity4':{'maxassessments':5, 'status':'open'},
        #        'ALL':{'maxassessments':5, 'status':'lock'}
        #        }

        for user in REQUEST.form['users']:

            # get USERACCESSRULES dictionary for user
            models = self.getUserAccessRules(user).models

            # adjust models dictionary for user to account for additional assessments
            for key in REQUEST.form.keys():
                if key.find('model') >= 0:
                    if REQUEST.form[key] != '0':
                        t = {}
                        if models.has_key(key):
                           t['maxassessments'] = int(models[key]['maxassessments']) + int(REQUEST.form[key])
                        else:
                           t['maxassessments'] = int(REQUEST.form[key])                          
                        t['status'] = 'open'
                        models[key] = t
                        
            self.getUserAccessRules(user)._p_changed = 1
                    
#####################################################################
## Class Initialisation

# Register Class Information with the Knowledge Engine
registerGenericObjectClass(UserAccessManager)

#####################################################################